/*
 * ============================================================================
 *
 *  Rotoblin
 *
 *  File:			rotoblin.blockscratch.sp
 *  Type:			Module
 *  Description:	Fixes some exploits for the infected, ghost ducking
 *					and scratching while being staggered.
 *
 *  Copyright (C) 2010  Mr. Zero <mrzerodk@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ============================================================================
 */

// --------------------
//       Private
// --------------------

static	const	String:	MAX_STAGGER_DURATION_CVAR[]				= "z_max_stagger_duration";
static			Float:	g_fStaggerDuration						= 0.9; // Default value
static			bool:	g_bProhibitMelee[MAXPLAYERS+1]			= {false};
static			Handle:	g_hProhibitMelee_Timer[MAXPLAYERS+1]	= {INVALID_HANDLE};

static	const	String:	CLASSNAME_TERRORPLAYER[] 				= "CTerrorPlayer";
static	const	String:	NETPROP_DUCKED[]						= "m_bDucked";
static	const	String:	NETPROP_DUCKING[]						= "m_bDucking";
static	const	String:	NETPROP_FALLVELOCITY[]					= "m_flFallVelocity";

static					g_iOffsetDucked							= -1;
static					g_iOffsetDucking						= -1;
static					g_iOffsetFallVelocity					= -1;

static	const	Float:	TELESPAWN_CLOSE_TO_SURVIVORS			= 50.0;
static	const	Float:	TELESPAWN_SPAWN_DELAY					= 0.1;

static					g_iDebugChannel							= 0;
static	const	String:	DEBUG_CHANNEL_NAME[]					= "InfectedExploitFixes";

/*static			Handle:	g_hDetectGhostDucking_Cvar				= INVALID_HANDLE;
static			bool:	g_bDetectGhostDucking					= false;*/

// **********************************************
//                   Forwards
// **********************************************

/**
 * Plugin is starting.
 *
 * @noreturn
 */
public _InfExloitFixes_OnPluginStart()
{
	g_iOffsetDucked = FindSendPropInfo(CLASSNAME_TERRORPLAYER, NETPROP_DUCKED);
	if (g_iOffsetDucked <= 0) ThrowError("Unable to find ducked offset!");

	g_iOffsetDucking = FindSendPropInfo(CLASSNAME_TERRORPLAYER, NETPROP_DUCKING);
	if (g_iOffsetDucking <= 0) ThrowError("Unable to find ducking offset!");

	g_iOffsetFallVelocity = FindSendPropInfo(CLASSNAME_TERRORPLAYER, NETPROP_FALLVELOCITY);
	if (g_iOffsetFallVelocity <= 0) ThrowError("Unable to find fall velocity offset!");

	/*decl String:buffer[2];
	IntToString(int:g_bDetectGhostDucking, buffer, sizeof(buffer));
	g_hDetectGhostDucking_Cvar = CreateConVarEx("block_ghost_ducking", buffer, "Sets whether players will be unduck when found to be ghost ducking", FCVAR_NOTIFY | FCVAR_PLUGIN);
	if (g_hDetectGhostDucking_Cvar == INVALID_HANDLE) ThrowError("Unable to create block ghost ducking cvar!");
	g_bDetectGhostDucking = GetConVarBool(g_hDetectGhostDucking_Cvar);*/

	HookPublicEvent(EVENT_ONPLUGINENABLE, _IEF_OnPluginEnable);
	HookPublicEvent(EVENT_ONPLUGINDISABLE, _IEF_OnPluginDisable);

	g_iDebugChannel = DebugAddChannel(DEBUG_CHANNEL_NAME);
	DebugPrintToAllEx("Module is now setup");
}

/**
 * Plugin is now enabled.
 *
 * @noreturn
 */
public _IEF_OnPluginEnable()
{
	HookEvent("player_shoved", _IEF_PlayerShoved_Event);
	HookEvent("player_spawn", _IEF_PlayerSpawn_Event);
	HookPublicEvent(EVENT_ONPLAYERRUNCMD, _IEF_OnPlayerRunCmd);

	g_fStaggerDuration = GetConVarFloat(FindConVar(MAX_STAGGER_DURATION_CVAR));
	HookConVarChange(FindConVar(MAX_STAGGER_DURATION_CVAR), _IEF_Stagger_CvarChange);

	/*g_bDetectGhostDucking = GetConVarBool(g_hDetectGhostDucking_Cvar);
	HookConVarChange(g_hDetectGhostDucking_Cvar, _IEF_DetectGhostDuck_CvarChange);*/

	DebugPrintToAllEx("Module is now loaded");
}

/**
 * Plugin is now disabled.
 *
 * @noreturn
 */
public _IEF_OnPluginDisable()
{
	UnhookEvent("player_shoved", _IEF_PlayerShoved_Event);
	UnhookEvent("player_spawn", _IEF_PlayerSpawn_Event);
	UnhookPublicEvent(EVENT_ONPLAYERRUNCMD, _IEF_OnPlayerRunCmd);

	UnhookConVarChange(FindConVar(MAX_STAGGER_DURATION_CVAR), _IEF_Stagger_CvarChange);
	//UnhookConVarChange(g_hDetectGhostDucking_Cvar, _IEF_DetectGhostDuck_CvarChange);

	DebugPrintToAllEx("Module is now unloaded");
}

/**
 * Detect ghost ducking cvar changed.
 *
 * @param convar		Handle to the convar that was changed.
 * @param oldValue		String containing the value of the convar before it was changed.
 * @param newValue		String containing the new value of the convar.
 * @noreturn
 */
/*public _IEF_DetectGhostDuck_CvarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
	DebugPrintToAllEx("Detect ghost ducking cvar was changed. Old value: %s, new value: %s", oldValue, newValue);
	g_bDetectGhostDucking = GetConVarBool(g_hDetectGhostDucking_Cvar);
}*/

/**
 * Stagger cvar changed.
 *
 * @param convar		Handle to the convar that was changed.
 * @param oldValue		String containing the value of the convar before it was changed.
 * @param newValue		String containing the new value of the convar.
 * @noreturn
 */
public _IEF_Stagger_CvarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
	DebugPrintToAllEx("Stagger cvar was changed. Old value: %s, new value: %s", oldValue, newValue);
	g_fStaggerDuration = GetConVarFloat(FindConVar(MAX_STAGGER_DURATION_CVAR));
}

/**
 * Called when a player is shoved.
 *
 * @param event			Handle to event.
 * @param name			String containing the name of the event.
 * @param dontBroadcast	True if event was not broadcast to clients, false otherwise.
 * @noreturn
 */
public _IEF_PlayerShoved_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (IsZACKLoaded()) return;
	new client = GetClientOfUserId(GetEventInt(event, "userid"));
	if (!client || GetClientTeam(client) != TEAM_INFECTED) return;

	if (!g_bProhibitMelee[client])
	{
		g_bProhibitMelee[client] = true;
	}
	else
	{
		KillTimer(g_hProhibitMelee_Timer[client]);
	}
	g_hProhibitMelee_Timer[client] = CreateTimer(g_fStaggerDuration, _IEF_PlayerShoved_Timer, client);
	DebugPrintToAllEx("Client %i: \"%N\" was shoved and is being prohibit from melee'ing", client, client);
}

/**
 * Called when a player is spawned.
 *
 * @param event			Handle to event.
 * @param name			String containing the name of the event.
 * @param dontBroadcast	True if event was not broadcast to clients, false otherwise.
 * @noreturn
 */
public _IEF_PlayerSpawn_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
	if (IsZACKLoaded()) return;
	new client = GetClientOfUserId(GetEventInt(event, "userid"));
	if (!client || IsFakeClient(client) || GetClientTeam(client) != TEAM_INFECTED) return;

	CreateTimer(TELESPAWN_SPAWN_DELAY, _IEF_CheckForTeleSpawn_Timer, client);

	/*if (g_bDetectGhostDucking)
	{*/
		// Check for ghost ducking
	if (!bool:GetEntData(client, g_iOffsetDucked, 1) ||			// If not ducked
		bool:GetEntData(client, g_iOffsetDucking, 1) ||			// or ducking
		(GetClientButtons(client) & IN_DUCK) ||					// or holding down duck
		GetEntDataFloat(client, g_iOffsetFallVelocity) != 0.0)	// or falling
		return;													// return

	SetEntData(client, g_iOffsetDucked, 0, 1, true); // Unduck player
	DebugPrintToAllEx("Client %i: \"%N\" was ghost ducking and were unducked. Offset status: ducked %b, ducking %b, in duck %b, fall vel %f", 
		client, client, 
		bool:GetEntData(client, g_iOffsetDucked, 1), 
		bool:GetEntData(client, g_iOffsetDucking, 1),
		bool:(GetClientButtons(client) & IN_DUCK),
		GetEntDataFloat(client, g_iOffsetFallVelocity));
	//}
}

/**
 * Called when check for tele spawn timer interval has elapsed.
 *
 * @param timer			Handle to the timer object.
 * @param client		Client index.
 * @noreturn
 */
public Action:_IEF_CheckForTeleSpawn_Timer(Handle:timer, any:client)
{
	if (!IsPlayerTeleSpawned(client)) return;
	TeleportEntity(client,
		Float:{0.0, 0.0, 0.0}, // Teleport to map center
		NULL_VECTOR, 
		NULL_VECTOR);
	ForcePlayerSuicide(client);
	DebugPrintToAllEx("Client %i: \"%N\" was found to be telespawning and was killed", client, client);
}

/**
 * Called when the player shoved timer interval has elapsed.
 *
 * @param timer			Handle to the timer object.
 * @param client		Client index of shoved player.
 * @noreturn
 */
public Action:_IEF_PlayerShoved_Timer(Handle:timer, any:client)
{
	g_bProhibitMelee[client] = false;
	DebugPrintToAllEx("Client %i is no longer prohibit from melee'ing", client);
}

/**
 * Called when a clients movement buttons are being processed.
 *
 * @param client		Index of the client.
 * @param buttons		Copyback buffer containing the current commands (as bitflags - see entity_prop_stocks.inc).
 * @param impulse		Copyback buffer containing the current impulse command.
 * @param vel			Players desired velocity.
 * @param angles		Players desired view angles.
 * @param weapon		Entity index of the new weapon if player switches weapon, 0 otherwise.
 * @noreturn
 */
public _IEF_OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon)
{
	if (g_bProhibitMelee[client] && buttons & IN_ATTACK2) // if melee'ing while being prohibit
	{
		buttons ^= IN_ATTACK2; // remove attack 2 from pressed buttons
		DebugPrintToAllEx("Client %i: \"%N\" tried to melee while being prohibit. Attack 2 removed from buttons", client, client);
	}
}

// **********************************************
//                 Private API
// **********************************************

/**
 * Checks location of infected player to see if they are within the survivor.
 *
 * @param client		Client index.
 * @return				True if within survivor, false otherwise.
 */
static IsPlayerTeleSpawned(client)
{
	if (client && 
		IsValidEntity(client) && 
		GetClientTeam(client) == TEAM_INFECTED && 
		IsPlayerAlive(client) &&
		SurvivorCount)
	{
		decl Float:origin[3], Float:surOrigin[3];
		GetClientAbsOrigin(client, origin);

		for (new i = 0; i < SurvivorCount; i++)
		{
			GetClientAbsOrigin(SurvivorIndex[i], surOrigin);
			if (GetVectorDistance(surOrigin, origin, true) <= TELESPAWN_CLOSE_TO_SURVIVORS) return true;
		}
	}
	return false;
}

/**
 * Wrapper for printing a debug message without having to define channel index
 * everytime.
 *
 * @param format		Formatting rules.
 * @param ...			Variable number of format parameters.
 * @noreturn
 */
static DebugPrintToAllEx(const String:format[], any:...)
{
	decl String:buffer[DEBUG_MESSAGE_LENGTH];
	VFormat(buffer, sizeof(buffer), format, 2);
	DebugPrintToAll(g_iDebugChannel, buffer);
}